#include <lib3d/node/text/SymbolAtlas.h>

#include <lib3d/node/text/AtlasMetricsHolder.h>
#include <font/GenericFont.h>
#include <font/AbstractFont.h>
#include <font/FontVisitor.h>
#include <font/TrueType.h>
#include <resource/Manager.h>
#include <xml/Stream.h>
#include <CompilerFeatures.h>

#include <TrueText/Atlas.h>
#include <TrueText/BitmapInfo.h>
#include <TrueText/TextMetrics.h>

namespace liba {
namespace lib3d {
namespace node {
namespace text {

class FontNameDefinition
	: public xml::ObjectANSI
{
public:
	FontNameDefinition()
		: target(0)
	{ }

	void SetTarget(std::string* value) { this->target = value; }

	virtual bool on_attribute(xml::Provider * prov, const std::string & name, const std::string & value) LIBA_OVERRIDE
	{
		if (name == "Name")
		{
			*target = value;
			return true;
		}

		return false;
	}
private:
	std::string* target;
};

class DictionaryValueDefinition
	: public xml::ObjectUnicode
{
public:
	DictionaryValueDefinition() : target(0) { }
	void SetTarget(std::wstring* value) { this->target = value; }

	virtual bool on_attribute(xml::Provider* prov, const std::wstring& name, const std::wstring& value) LIBA_OVERRIDE
	{
		if (name == L"Value")
		{
			*target = value;
			return true;
		}
		return false;
	}
private:
	std::wstring* target;
};

class SymbolAtlasRepDefinition
	: public xml::ObjectUnicode
{
public:
	virtual bool on_node(xml::Provider * prov, const std::wstring & name) LIBA_OVERRIDE;

	const std::string& GetFontName() const { return this->fontName; }
	const std::wstring& GetDictionary() const { return this->dictionary; }
private:
	std::string fontName;
	std::wstring dictionary;
};

bool SymbolAtlasRepDefinition::on_node(xml::Provider * prov, const std::wstring & name)
{
	if (name == L"Font")
	{
		FontNameDefinition fndef;
		fndef.SetTarget(&this->fontName);
		return prov->parse_object(&fndef);
	}
	else if (name == L"Dictionary")
	{
		DictionaryValueDefinition dvdef;
		dvdef.SetTarget(&this->dictionary);
		return prov->parse_object(&dvdef);
	}
	return false;
}

struct SymbolAtlasRepPrivates
{
	AtlasMetricsHolder atlasMetrics;
	std::auto_ptr<font::GenericFont> genericFont;
	font::TrueType* trueTypeFont;
	std::string fontName;
	std::wstring dictionary;

	int width;
	int height;
};

SymbolAtlasRep::SymbolAtlasRep(const filesystem::String& name)
	: privates(new SymbolAtlasRepPrivates())
{
	privates->width = 0;
	privates->height = 0;

	// 1. Load definitions from resource
	std::auto_ptr<resource::Resource> atlasResource(resource::Manager::create_resource(name));
	std::auto_ptr<SymbolAtlasRepDefinition> atlasDefinition(new SymbolAtlasRepDefinition());
	if (!xml::StreamResource(atlasResource.get()).load(&xml::Root(atlasDefinition.get(), "SymbolAtlas")))
		THROW(GluckBadResource(__FUNCTION__, "Failed to load symbol atlas definition resource", name));

	// 2. Extract TrueType font

	std::auto_ptr<font::GenericFont> genericFont(new font::GenericFont(atlasDefinition->GetFontName()));

	class TrueTypeExtractorVisitor
		: public font::FontVisitor
	{
	public:
		TrueTypeExtractorVisitor()
			: ttf(0)
		{ }

		virtual void Visit(font::Proportional* font) LIBA_OVERRIDE
		{
			THROW(Gluck(__FUNCTION__, "Symbol atlases cannot be generated over proportional fonts"));
		}

		virtual void Visit(font::TrueType* font) LIBA_OVERRIDE
		{
			ttf = font;
		}
		
		virtual void Visit(SymbolAtlas* font) LIBA_OVERRIDE
		{
			THROW(Gluck(__FUNCTION__, "Symbol atlases cannot be generated over another symbol atlas"));
		}

		font::TrueType* GetTrueTypeFont() const { return ttf; }
	private:
		font::TrueType* ttf;
	};

	TrueTypeExtractorVisitor v;
	genericFont->GetAbstractFont()->Accept(&v);
	this->privates->genericFont = genericFont;
	this->privates->trueTypeFont = v.GetTrueTypeFont();

	if (!this->privates->trueTypeFont)
		THROW(Gluck(__FUNCTION__, "Given font isn't a true type one"));

	this->privates->dictionary = atlasDefinition->GetDictionary();

	hard::Manager::add_dependent(this);
}

SymbolAtlasRep::~SymbolAtlasRep()
{
	hard::Manager::remove_dependent(this);
}

void SymbolAtlasRep::on_add_hardware(hard::Manager::hardware_type* ha)
{
	if (ha->is_operational())
		hard_vector.push_back(SweetPair(ha, createTexture(ha).release()));
}

class TextureLock
{
public:
	TextureLock(hard::Texture* tex)
		: tex(tex)
	{
		ptr_ = tex->lock(0, &stride_);
	}

	~TextureLock()
	{
		tex->unlock(0);
	}

	void* buffer() { return ptr_; }
	int stride() { return stride_; }
private:
	int stride_;
	void* ptr_;
	hard::Texture* tex;
};

std::auto_ptr<hard::Texture> SymbolAtlasRep::createTexture(hard::Manager::hardware_type* ha)
{
	TrueText::Atlas a(*this->privates->trueTypeFont->Font());

	// 1. Figure out dimensions of our atlas
	size_t w = 64;
	size_t h = 64;
	a.SetClipWidth(w);
	a.SetString(this->privates->dictionary.c_str());
	bool increaseW = true;

	while (a.GetBounds().GetDimensions().Height > h)
	{
		if (increaseW)
			w <<= 1;
		else
			h <<= 1;

		a.SetClipWidth(w);

		increaseW = !increaseW;
	}

	privates->width = w;
	privates->height = h;

	// 2. Render atlas to texture
	std::auto_ptr<hard::Texture> tex(ha->create_texture(0, pictureformat::A8R8G8B8, w, h));
	{
		TextureLock data(tex.get());

		//clear data buffer first
		for(size_t i = 0; i < h; ++i)
		{
			//Texture is always PIXEL_FORMAT_ARGB_8888
			memset(((char *)data.buffer()) + i*data.stride(), 0, w*4);
		}

		TrueText::BitmapInfo bi;
		bi.SetBits(data.buffer());
		bi.SetPitch(data.stride());
		bi.SetPixelFormat(TrueText::PIXEL_FORMAT_ARGB_8888);
		bi.SetBounds(TrueText::Dimensions(w, h));

		a.Render(bi);
	}

	// 3. Store metrics
	this->privates->atlasMetrics.Value = a.GetMetrics();

	return tex;
}

int SymbolAtlasRep::GetWidth() const
{
	return privates->width;
}

int SymbolAtlasRep::GetHeight() const
{
	return privates->height;
}

void SymbolAtlasRep::render_to_list( hard::Hardware * hardware, hard::RenderTexture * render_texture )
{
	render_texture->texture = find_data(hardware);
	render_texture->alpha_channel = true;
}

const AtlasMetricsHolder& SymbolAtlasRep::GetAtlasMetrics() const
{
	return privates->atlasMetrics;
}

SymbolAtlas::SymbolAtlas(const filesystem::String& name)
{
	load_rep(name);
}

void SymbolAtlas::render_to_list(hard::Hardware* hardware, hard::RenderTexture* render_texture)
{
	get_rep()->render_to_list(hardware, render_texture);
}

void SymbolAtlas::Accept(font::FontVisitor* visitor)
{
	visitor->Visit(this);
}

font::AbstractFont* SymbolAtlas::clone()
{
	return new SymbolAtlas(*this);
}

} // namespace text
} // namespace node
} // namespace lib3d
} // namespace liba